﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Workflow.Runtime;
using System.Threading;
using System.Workflow.ComponentModel.Compiler;
using System.Diagnostics;
using System.Reflection;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.Runtime.Hosting;

using TextFlow.Activities;

namespace TextFlow.Engine
{
    public class TextFlowExecutor
    {
        private List<object> _svcs = new List<object>();
        private List<Assembly> _assemblies = new List<Assembly>();

        public event EventHandler<WorkflowCompletedEventArgs> WorkflowCompleted;
        public event EventHandler<WorkflowTerminatedEventArgs> WorkflowTerminated;
        public event EventHandler<WorkflowSuspendedEventArgs> WorkflowSuspended;

        public TextFlowExecutor()
        {
        }

        public void AddService( object service )
        {
            if ( service == null )
            {
                throw new ArgumentNullException( "service" );
            }

            if ( service is WorkflowLoaderService )
            {
                throw new InvalidOperationException( "Cannot configure a custom loader against TextFlowExecutor." );
            }

            if ( service is TypeProvider )
            {
                throw new InvalidOperationException( "Cannot configure a type provider against TextFlowExecutor. Consider adding assembly references via AddAssembly() instead." );
            }

            if ( _svcs.Contains( service ) == false )
            {
                _svcs.Add( service );
            }
        }

        public void AddAssembly( Assembly assembly )
        {
            if ( assembly == null )
            {
                throw new ArgumentNullException( "assembly" );
            }

            if ( _assemblies.Contains( assembly ) == false )
            {
                _assemblies.Add( assembly );
            }
        }

        public void Execute( string languageText )
        {
            Debug.Assert( _svcs != null );
            Debug.Assert( _assemblies != null );

            if ( string.IsNullOrEmpty( languageText ) )
            {
                throw new ArgumentNullException( "languageText" );
            }

            using ( WorkflowRuntime workflowRuntime = new WorkflowRuntime() )
            {
                AutoResetEvent waitHandle = new AutoResetEvent( false );

                foreach ( EventHandler<WorkflowCompletedEventArgs> handler in WorkflowCompleted.GetInvocationList() )
                {
                    workflowRuntime.WorkflowCompleted += handler;
                }

                workflowRuntime.WorkflowCompleted += delegate { waitHandle.Set(); };

                foreach ( EventHandler<WorkflowTerminatedEventArgs> handler in WorkflowTerminated.GetInvocationList() )
                {
                    workflowRuntime.WorkflowTerminated += handler;
                }

                workflowRuntime.WorkflowTerminated += delegate { waitHandle.Set(); };

                foreach ( EventHandler<WorkflowSuspendedEventArgs> handler in WorkflowSuspended.GetInvocationList() )
                {
                    workflowRuntime.WorkflowSuspended += handler;
                }

                workflowRuntime.WorkflowSuspended += delegate( object sender, WorkflowSuspendedEventArgs arg )
                {
                    if ( arg.Error.StartsWith( "__internalsuspend:" ) )
                    {
                        waitHandle.Set();
                    }
                };

                try
                {
                    ActivitySurrogateSelector.Default.ChainSelector( new ExpressionSerializerSelector() );    // hook Expression serializer into runtime...

                    workflowRuntime.AddService( new TextFlowLoader() );

                    _svcs.ForEach( svc => workflowRuntime.AddService( svc ) );

                    AddAssembliesToRuntime( workflowRuntime );

                    using ( XmlReader reader = GetReaderForLanguageText( languageText ) )
                    {
                        WorkflowInstance instance = workflowRuntime.CreateWorkflow( reader );

                        Debug.Assert( instance != null );

                        instance.Start();

                        waitHandle.WaitOne();
                    }
                }
                finally
                {
                    foreach ( EventHandler<WorkflowCompletedEventArgs> handler in WorkflowCompleted.GetInvocationList() )
                    {
                        workflowRuntime.WorkflowCompleted -= handler;
                    }

                    foreach ( EventHandler<WorkflowTerminatedEventArgs> handler in WorkflowTerminated.GetInvocationList() )
                    {
                        workflowRuntime.WorkflowTerminated -= handler;
                    }

                    foreach ( EventHandler<WorkflowSuspendedEventArgs> handler in WorkflowSuspended.GetInvocationList() )
                    {
                        workflowRuntime.WorkflowSuspended -= handler;
                    }
                }
            }
        }

        private void AddAssembliesToRuntime( WorkflowRuntime workflowRuntime )
        {
            Debug.Assert( workflowRuntime != null );
            Debug.Assert( _assemblies != null );

            TypeProvider typeProv = new TypeProvider( workflowRuntime );

            typeProv.AddAssembly( typeof( TextFlowWorkflow ).Assembly );

            _assemblies.AddRange( DynamicActivityGenerator.Assemblies );

            _assemblies.ForEach( asm => typeProv.AddAssembly( asm ) );

            workflowRuntime.AddService( typeProv );
        }

        private XmlReader GetReaderForLanguageText( string languageText )
        {
            if ( string.IsNullOrEmpty( languageText ) )
            {
                throw new ArgumentNullException( "languageText" );
            }

            using ( Stream template = Assembly.GetExecutingAssembly().GetManifestResourceStream( "TextFlow.Engine.input_template.xml" ) )
            {
                Debug.Assert( template != null );

                using ( StreamReader reader = new StreamReader( template, Encoding.UTF8 ) )
                {
                    string input = string.Format( reader.ReadToEnd(), languageText.Trim() );

                    Debug.Assert( string.IsNullOrEmpty( input ) == false );

                    return XmlReader.Create( new StringReader( input ) );
                }
            }
        }
    }
}
